<?php
/**
 * Celebrio Windows Azure storage table appender for log4php
 *
 * @copyright  Copyright (c) 2012 Celebio Software, s.r.o.
 */
namespace Celebrio\Diagnostics;

use LoggerAppender;
use LoggerLoggingEvent;

use Microsoft_WindowsAzure_Storage_DynamicTableEntity;
use Microsoft_WindowsAzure_Exception;

use Celebrio\WindowsAzure\AzureStorage\AzureStorageTable;
use Celebrio\Logging\AzureAppenderException;

/**
 * Azure Appender is designed to be compatible with Apache Log4PHP appenders.
 *
 * @author Martin Nov�k
 */
class LoggerAppenderAzureTable extends \LoggerAppender {

	/**
	 * @var array of logged events
	 */
	private $events;
	/**
	 * @var string the table name used to append events
	 */
	protected $table;

    public function __construct() {
		parent::__construct();
    }

    /**
     * Prepares the appender for logging.
     */
    public function activateOptions() {
        parent::activateOptions();
    }

    /**
	 * Converts the logging event into an array which can be logged to Windows Azure storage table.
	 * 
	 * @param LoggerLoggingEvent $event
	 * @return array The array representation of the logging event.
	 */
	protected function format(LoggerLoggingEvent $event) {

		$logEntity = new Microsoft_WindowsAzure_Storage_DynamicTableEntity(date('Ymd'), microtime(true));

		$document = array(
			'level'      => $event->getLevel()->toString(),
			'thread'     => (int) $event->getThreadName(),
			'message'    => $event->getRenderedMessage(),
			'loggerName' => $event->getLoggerName() 
		);
		
		$throwableInfo = $event->getThrowableInformation();

		$locationInfo = $event->getLocationInformation();
		if ($locationInfo != null) {
			$document['fileName']   = $locationInfo->getFileName();
			$document['method']     = $locationInfo->getMethodName();
			$document['lineNumber'] = ($locationInfo->getLineNumber() == 'NA') ? 'NA' : (int) $locationInfo->getLineNumber();
			$document['className']  = $locationInfo->getClassName();
		}	

		$throwableInfo = $event->getThrowableInformation();
		if ($throwableInfo != null) {
			$exception = $this->formatThrowable($throwableInfo->getThrowable());
			if(is_array($exception)) $document = array_merge($document,$exception);
		}
				
		foreach ($document as $key => $value) {
			 if ((is_object($value) && !method_exists($value,'__toString'))
                || is_array($value)) {

                $value = gettype($value);
            }

			$logEntity->{$key} = $value;
		}

		return $logEntity;
	}
	
	/**
	 * Converts an Exception into an array which can be logged to Windows Azure storage.
	 * 
	 * Supports innner exceptions (PHP >= 5.3)
	 * 
	 * @param Exception $ex
	 * @return array
	 */
	protected function formatThrowable(\Exception $ex, $inner = false) {
		$array = null;
		if(!$inner) {
			$array = array(				
				'exceptionMessage'    => $ex->getMessage(),
				'exceptionCode'       => $ex->getCode(),
				'exceptionStackTrace' => $ex->getTraceAsString(),
			);
		} else {
			$array = array(				
				'innerExceptionMessage'    => $ex->getMessage(),
				'innerExceptionCode'       => $ex->getCode(),
				'innerExceptionStackTrace' => $ex->getTraceAsString(),
			);
		}
                        
		if (method_exists($ex, 'getPrevious') && $ex->getPrevious() !== null) {
			$innerException = $this->formatThrowable($ex->getPrevious(), true);
			if(is_array($exception)) $array = array_merge($array,$innerException);
		}

		return $array;
	}

    /**
     * Forwards the logging event to the destination.
     *
     * @param \LoggerLoggingEvent $event
     */
    protected function append(LoggerLoggingEvent $event) {
    	if($this->getTable() == "log4phpTimer") throw new \InvalidArgumentException("log4phpTimer table is used by Celebrio Timer in Celebrio\Diagnostics\LoggerAppenderAzureTimer");
        $this->addEvent($this->format($event));
    }
    
    /**
     * Adds formated event to be logged
     * @param array $event
     */
    protected function addEvent($event){
    	$rowKey = $event->getRowKey();
    	$found = FALSE;
    	for($i = 0; $i < count($this->events); $i++){
    		if($this->events[$i]->getRowKey() == $rowKey){
    			if(isset($this->events[$i]->{'time'})) { // if its timer then the value is updated, otherwise the event is ignored (but that should not happend because common events have different rowkey politics)
    				$this->events[$i]->{'time'} = ($event->{'time'} + $this->events[$i]->{'time'})/2;
    			}
    			$found = TRUE;
    			break;
    		}
    	}
    	if(!$found)$this->events[] = $event;
    }
    
    /**
     * gets all events to be logged
     * @return array
     */
    protected function getEvents(){
    	return $this->events;
    }


    /**
     * Releases any resources allocated by the appender. This method
     * is called every time the script processing is finished.
     */
    public function close() {
    	$events = $this->getEvents();
    	if (is_array($events)){
	    	$logTableName = $this->getTable();
	    	
	    	// TODO: this should not be called every time as it makes unnecessary connections
	    	AzureStorageTable::connection()->createTableIfNotExists($logTableName);
			try {
		    	$batch = AzureStorageTable::connection()->startBatch();
		    	$counter = 0;
				foreach ($events as $logEntity) {
					if($counter == 90){
		    			$batch->commit();
		    			$batch = AzureStorageTable::connection()->startBatch();
		    			$counter = 0;
		    		}
					AzureStorageTable::connection()->insertEntity($logTableName, $logEntity);
					$counter++;
				}
				$batch->commit();
			} catch (Microsoft_WindowsAzure_Exception $e) {
			//var_export($events);
				throw new AzureAppenderException("Error while storing logs to Windows Azure storage Table. Message: ".$e->getMessage());
			}
    	}
        parent::close();
    }

	/**
	 * Sets the type where the log output will go.
	 * @param string $file
	 */
	public function setTable($table) {
		$this->setString('table', $table);
	}
	
	/**
	 * @return string
	 */
	public function getTable() {
		return $this->table;
	}

}
